pygrub: support parsing of syslinux configuration files
authorKeir Fraser <keir.fraser@citrix.com>
Mon, 1 Feb 2010 14:03:47 +0000 (14:03 +0000)
committerKeir Fraser <keir.fraser@citrix.com>
Mon, 1 Feb 2010 14:03:47 +0000 (14:03 +0000)
Allows booting from ISOs which use isolinux as well as guests using
extlinux.

Also add copyright header to GrubConf.py, I think the grub2 support
added last year qualifies as a substantial change.

Signed-off-by: Ian Campbell <ian.campbell@citrix.com>
tools/pygrub/src/ExtLinuxConf.py [new file with mode: 0644]
tools/pygrub/src/GrubConf.py
tools/pygrub/src/pygrub

diff --git a/tools/pygrub/src/ExtLinuxConf.py b/tools/pygrub/src/ExtLinuxConf.py
new file mode 100644 (file)
index 0000000..99f0990
--- /dev/null
@@ -0,0 +1,200 @@
+#
+# ExtLinuxConf.py - Simple syslinux config parsing
+#
+# Copyright 2010 Citrix Systems Ltd.
+#
+# This software may be freely redistributed under the terms of the GNU
+# general public license.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+import sys, re, os
+import logging
+import GrubConf
+
+class ExtLinuxImage(object):
+    def __init__(self, lines, path):
+        self.reset(lines, path)
+
+    def __repr__(self):
+        return ("title: %s\n"
+                "  root: %s\n"
+                "  kernel: %s\n"
+                "  args: %s\n"
+                "  initrd: %s\n" %(self.title, self.root, self.kernel,
+                                   self.args, self.initrd))
+    def reset(self, lines, path):
+        self._initrd = self._kernel = self._readonly = None
+        self._args = ""
+        self.title = ""
+        self.lines = []
+        self.path = path
+        self.root = ""
+        map(self.set_from_line, lines)
+
+    def set_from_line(self, line, replace = None):
+        (com, arg) = GrubConf.grub_exact_split(line, 2)
+        com = com.lower()
+
+        # Special handling for mboot.c32
+        if com.lower() == "append" and self.kernel is not None:
+            (_,kernel) = self.kernel
+            if kernel.endswith("mboot.c32"):
+                kernel = None
+                args = None
+                initrd = None
+                modules = arg.split("---")
+
+                if len(modules) == 3: # Assume Xen + Kernel + Initrd
+                    (_,kernel,initrd) = modules
+                elif len(modules) == 2: # Assume Kernel + Initrd
+                    (kernel,initrd) = modules
+
+                if kernel:
+                    setattr(self, "kernel", kernel.strip())
+                if initrd:
+                    setattr(self, "initrd", initrd.strip())
+
+                # Bypass regular self.commands handling
+                com = None
+
+        if com is not None and self.commands.has_key(com):
+            if self.commands[com] is not None:
+                setattr(self, self.commands[com], re.sub('^"(.+)"$', r"\1", arg.strip()))
+            else:
+                logging.info("Ignored image directive %s" %(com,))
+        elif com is not None:
+            logging.warning("Unknown image directive %s" %(com,))
+
+        # now put the line in the list of lines
+        if replace is None:
+            self.lines.append(line)
+        else:
+            self.lines.pop(replace)
+            self.lines.insert(replace, line)
+
+    def set_kernel(self, val):
+        if val.find(" ") == -1:
+            self._kernel = (None,val)
+            self._args = None
+            return
+        (kernel, args) = val.split(None, 1)
+        self._kernel = (None,kernel)
+        self._args = args
+    def get_kernel(self):
+        return self._kernel
+    def get_args(self):
+        return self._args
+    kernel = property(get_kernel, set_kernel)
+    args = property(get_args)
+
+    def set_initrd(self, val):
+        self._initrd = (None,val)
+    def get_initrd(self):
+        return self._initrd
+    initrd = property(get_initrd, set_initrd)
+
+    def set_readonly(self, val):
+        self._readonly = 1
+    def get_readonly(self):
+        return self._readonly
+    readonly = property(get_readonly, set_readonly)
+
+    # set up command handlers
+    commands = {
+        "label": "title",
+        "kernel": "kernel",
+        "append": "args"}
+
+class ExtLinuxConfigFile(object):
+    def __init__(self, fn = None):
+        self.filename = fn
+        self.images = []
+        self.timeout = -1
+        self._default = 0
+
+        if fn is not None:
+            self.parse()
+
+    def parse(self, buf = None):
+        if buf is None:
+            if self.filename is None:
+                raise ValueError, "No config file defined to parse!"
+
+            f = open(self.filename, 'r')
+            lines = f.readlines()
+            f.close()
+        else:
+            lines = buf.split("\n")
+
+        path = os.path.dirname(self.filename)
+        img = []
+        for l in lines:
+            l = l.strip()
+            # skip blank lines
+            if len(l) == 0:
+                continue
+            # skip comments
+            if l.startswith('#'):
+                continue
+            # new image
+            if l.lower().startswith("label"):
+                if len(img) > 0:
+                    self.add_image(ExtLinuxImage(img, path))
+                img = [l]
+                continue
+
+            if len(img) > 0:
+                img.append(l)
+                continue
+
+            (com, arg) = GrubConf.grub_exact_split(l, 2)
+            com = com.lower()
+            if self.commands.has_key(com):
+                if self.commands[com] is not None:
+                    setattr(self, self.commands[com], arg.strip())
+                else:
+                    logging.info("Ignored directive %s" %(com,))
+            else:
+                logging.warning("Unknown directive %s" %(com,))
+
+        if len(img) > 0:
+            self.add_image(ExtLinuxImage(img, path))
+
+    def hasPassword(self):
+        return False
+
+    def hasPasswordAccess(self):
+        return True
+
+    def add_image(self, image):
+        self.images.append(image)
+
+    def _get_default(self):
+        for i in range(len(self.images)):
+            if self.images[i].title == self._default:
+                return i
+        return 0
+    def _set_default(self, val):
+        self._default = val
+    default = property(_get_default, _set_default)
+
+    commands = { "default": "default",
+                 "timeout": "timeout",
+                 "serial": None,
+                 "prompt": None,
+                 "display": None,
+                 "f1": None,
+                 "f2": None,
+                 }
+        
+if __name__ == "__main__":
+    if sys.argv < 2:
+        raise RuntimeError, "Need a configuration file to read"
+    g = ExtLinuxConfigFile(sys.argv[1])
+    for i in g.images:
+        print i
+    print g.default
index 58a448ca20655063bd43576d562e689c7afa03a1..60b7f50e6618c8538a0c9ea52dce9c250317e261 100644 (file)
@@ -1,6 +1,7 @@
 #
 # GrubConf.py - Simple grub.conf parsing
 #
+# Copyright 2009 Citrix Systems Inc.
 # Copyright 2005-2006 Red Hat, Inc.
 # Jeremy Katz <katzj@redhat.com>
 #
index c2870853c2cc63afb54e36a43b37a2ff35aa67cf..1b1ed986c35ed0c3054069091c2e192418cd6468 100644 (file)
@@ -24,6 +24,7 @@ import getopt
 import fsimage
 import grub.GrubConf
 import grub.LiloConf
+import grub.ExtLinuxConf
 
 PYGRUB_VER = 0.6
 
@@ -383,7 +384,10 @@ class Grub:
                            ["/boot/grub/menu.lst", "/boot/grub/grub.conf",
                             "/grub/menu.lst", "/grub/grub.conf"]) + \
                        map(lambda x: (x,grub.GrubConf.Grub2ConfigFile),
-                           ["/boot/grub/grub.cfg"])
+                           ["/boot/grub/grub.cfg"]) + \
+                       map(lambda x: (x,grub.ExtLinuxConf.ExtLinuxConfigFile),
+                           ["/boot/isolinux/isolinux.cfg",
+                            "/boot/extlinux.conf"])
 
         if not fs:
             # set the config file and parse it